Skip to content

feat(spec): declare MetadataProtocol.historyMetaItem and de-cast the REST history door - #13521

Merged
os-project-manager merged 5 commits into
mainfrom
claude/issue-12005-history-meta-item-declared
Aug 31, 2026
Merged

feat(spec): declare MetadataProtocol.historyMetaItem and de-cast the REST history door#13521
os-project-manager merged 5 commits into
mainfrom
claude/issue-12005-history-meta-item-declared

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes #12005

Declares the historyMetaItem member on MetadataProtocol and de-casts the REST history door — the audit door's named twin, following the #11678/PR #12003 pattern member for member (sibling-inheritance grading per triage; the #11006 maintainer-ruled pattern, 2026-08-22 option B).

What changed

packages/spec/src/api/protocol.zod.ts

  • HistoryMetaItemRequestSchema / HistoryMetaItemRequest{ type, name, organizationId?, sinceSeq?, limit? }, mirroring ObjectStackProtocolImplementation.historyMetaItem's parameter type (packages/metadata-protocol/src/protocol.ts) member for member.
  • HistoryMetaItemResponseSchema / HistoryMetaItemResponse{ events: MetadataEvent[] }, transcribing MetadataEventSchema from @objectstack/metadata-core (ADR-0008 §2.4; spec cannot import that package — same transcription approach as the audit twin's event rows).
  • MetadataProtocol.historyMetaItem?(...) — optional, like its auditMetaItem / deleteMetaItem / getMetaItemLayered siblings.

packages/rest/src/rest-server.ts (GET /meta/:type/:name/history)

  • Guard if (!(p as any).historyMetaItem)if (!p.historyMetaItem) — identical truthiness semantics, minus the cast.
  • Call (p as any).historyMetaItem({...}) → a named const typed TransportScopedMetaRequest<HistoryMetaItemRequest> — the reset-door spelling, not the audit door's plain request type, because this door still spreads the transport-level environmentId (see member map). Same keys, same conditional spreads, byte-identical wire payload.

Tests: three capture/pin suites in packages/spec/src/api/protocol.test.ts mirroring the #11678 blocks (request mirror + value preservation, response body incl. honest-empty and closed op vocabulary, type-level member pins with @ts-expect-error on undeclared keys). Two new isomorphism pins (Iso863/Iso864) in type-alias-convention.pin.test.ts (833 → 835, ledger paragraph appended).

Changesets: @objectstack/spec minor + @objectstack/rest patch — the same class as PR #12003's pair (spec-audit-meta-item-member / rest-meta-audit-reset-casts-retired).

Per-member map (inheriting PR #12003's verdicts)

Member Verdict Why / precedent
type, name declared, required z.string() door sends, implementation requires — same as auditMetaItem (#12003)
organizationId declared, z.string().optional() — NOT nullable #12003's rule is "mirror the implementation's parameter type member for member". Audit's implementation declares string | null and its door always sends ctx?.tenantId ?? null, hence .nullable() there; history's implementation declares plain organizationId?: string and this door sends no organization at all. Whether the door SHOULD send one is the #8747-family measurement the card fences to a future issue — deliberately unanswered here.
sinceSeq declared, z.number().optional() door sends (Number.isFinite-guarded), implementation reads; exclusive lower bound (HistoryOptions.sinceSeq). No audit equivalent; treated by the same rule as limit.
limit declared, z.number().optional(), no bounds door sends, implementation reads. Unlike audit's [1, 500] clamp, history forwards unclamped with no default — declaring .min()/.max() would refuse values the shipped verb accepts (the same no-accept/reject-drift rule #12003 recorded for audit's clamp).
environmentId NOT declared; wire spread kept transport-level by the #9741 ruling; rides TransportScopedMetaRequest exactly like the reset door in #12003's second half. #12003 did not remove the audit equivalent (that was #8747, earlier), so removing this door's dead spread is not inherited — recorded below instead.
response events MetadataEvent[] transcription, op enum closed per the implementation's return type; closed vocabularies mirror #12003's treatment of operation/outcome. ref.type is a plain string, not the registry enum — plugin runtime-create types flow through this door (the #12038 1C anti-freezing reasoning); ref.name carries no regex, same rationale.

Deliberately NOT in this PR (per card fences)

Verification

All at final head 4bcd09f0 (the union re-ran after the last commit):

  • pnpm --filter @objectstack/spec typecheck && pnpm --filter @objectstack/spec test — exit 0 via the shared verify lock; vitest: Test Files 442 passed | 1 skipped (443) · Tests 11819 passed | 1 skipped (11820).
  • pnpm exec turbo run typecheck --filter=@objectstack/rest (Tasks: 25 successful, 25 total) then pnpm --filter @objectstack/rest test — exit 0; vitest: Test Files 161 passed (161) · Tests 2703 passed (2703) (includes rest-server-query-multiplicity.test.ts, which pins the history door's { limit, sinceSeq } forwarding — behaviour unchanged).
  • pnpm --filter @objectstack/spec check:generated — exit 0 at 4bcd09f0 (earlier run proved 4 artifacts stale and --fix regenerated exactly those; the committed diff carries them).
  • Derived families re-run at 4bcd09f0: check:spec-parsed-alias, check:route-envelope (ratchet counts untouched — the 501 literal is byte-identical), check:nul-bytes — all exit 0. Earlier at the pre-docs-fix head, also green: check:authz-resolver, check:dispatcher-error-vocabulary, check:test-source-alias, check:quick-reference-counts, check:doc-anchors, check:docs-single-h1, check:llms-txt, check:doc-authoring (this one first went RED on two internal issue ids in the new .describe() strings — a real catch; fixed in the second commit and green since).
  • The remaining repo-wide families run in CI as usual.

Ablation (de-cast proof)

Both legs rebuilt @objectstack/spec and proved the tree state on disk (full log in the report comment's transcript):

  • Mutation: protocol.zod.ts + the four generated spec artifacts reverted to the pre-declaration base 602d4a0a; proven by grep (historyMetaItem src count 3 → 0, manifest 0) and by ablation-dist-preflight --absent after the rebuild (marker gone from all 215 dist files). First attempt reverted only the source and the spec build itself refused (manifest still listed the new schemas) — a null run, redone as a five-path mutation.
  • Predicted red, observed red: pnpm --filter @objectstack/rest typecheck exit 2 — TS2724 '"@objectstack/spec/api"' has no exported member named 'HistoryMetaItemRequest' + TS2339 Property 'historyMetaItem' does not exist on type 'RestProtocol' at both the guard (:6006) and the call (:6040). (Predicted TS2305 for the import; tsc emits the near-name variant TS2724 — same missing-export class.)
  • Restore: all 5 blobs hash-equal to HEAD, rebuild green, ablation-dist-preflight finds the marker back in dist/api/index.d.ts/d.mts, whole-tree git status clean, rest typecheck exit 0.

The script carried an EXIT/INT/TERM trap with absolute paths; the hash comparison, not the trap, is the restore proof.


Generated by Claude Code

claude added 2 commits August 30, 2026 15:04
…REST history door

The history door schemas mirror the implementation's parameter and return
types member for member (the #11006 pattern, carried one door over exactly
as #11678 carried it to the audit twin). The REST door literal now compiles
against the declared contract through TransportScopedMetaRequest; wire
payload byte-identical.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KX8wnyjStaZcuMyAMNsy3N
check:doc-authoring measured them in the customer-facing describe
population; the reasoning stays in the TSDoc comments, which are internal.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KX8wnyjStaZcuMyAMNsy3N
@github-actions

github-actions Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/rest, @objectstack/spec, touching 10 documentable anchor(s). ⚠️ 5 changed file(s) yielded no anchor (packages/rest/src/rest-server.ts, packages/spec/api-surface/api.json, packages/spec/authorable-surface/api.json, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/kernel/contracts/metadata-service.mdx (via getHistory (sdk))
  • content/docs/kernel/services-checklist.mdx (via MetadataProtocol (symbol))
  • content/docs/plugins/adding-a-metadata-type.mdx (via /:type/:name/history (route), /api/v1/meta/:type/:name/history (route))

2 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v15.mdx (via MetadataProtocol (symbol))
  • content/docs/releases/v17.mdx (via MetadataProtocol (symbol))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 5 changed file(s) yielded no anchor (packages/rest/src/rest-server.ts, packages/spec/api-surface/api.json, packages/spec/authorable-surface/api.json, …) — pages documenting those are invisible to this run
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 128 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json ff3757697600591804e2135a1e58b7afaebe0a48packageMentionDocs.

Which tree this was computed on

This run read content/docs from 1d98ed391a9e9821a18dd85874631b70766b77ed — the merge of head c8a5d8b48ccdebd1db52b7e91f174c0e1ddbdf3c into base ff3757697600591804e2135a1e58b7afaebe0a48, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 1d98ed391a9e9821a18dd85874631b70766b77ed && git checkout 1d98ed391a9e9821a18dd85874631b70766b77ed
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin ff3757697600591804e2135a1e58b7afaebe0a48 c8a5d8b48ccdebd1db52b7e91f174c0e1ddbdf3c && git checkout -B drift-repro ff3757697600591804e2135a1e58b7afaebe0a48 && git merge --no-ff c8a5d8b48ccdebd1db52b7e91f174c0e1ddbdf3c

node scripts/docs-audit/affected-docs.mjs --json ff3757697600591804e2135a1e58b7afaebe0a48

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs ff3757697600591804e2135a1e58b7afaebe0a48 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

claude added 3 commits August 30, 2026 16:31
…ons the history-door diff shifted

check-system-context-census (CI 'Lint & Repo Gates') caught pure line rot:
the new import line shifted every site below it by +1 and the de-cast door
block shifted the two sites below it by +19. The gate's --fix refused (its
population sanity compares page anchors against census reads without folding
in the two NON_READ_ANCHORS-excused seams), so the eight anchors are
rewritten by hand to the lines the census and ledger already resolve to.
Census now: 109 sites all anchored, 145 anchors resolve, 27 declared
non-read.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KX8wnyjStaZcuMyAMNsy3N
Re-anchor the eight rest-server.ts isSystem citations on the merged tree:
both sides had re-anchored for their own shifts, so the merged file needed
line numbers re-derived by the census (check-system-context-census: OK).

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KX8wnyjStaZcuMyAMNsy3N
Discharges the regeneration the merge commit deferred (os-regen).

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KX8wnyjStaZcuMyAMNsy3N

Copy link
Copy Markdown
Collaborator Author

Contract review (fable seat, maintainer-directed): PASS.

The request/response schemas mirror the implementation member-for-member, with the two divergences argued correctly: organizationId plain optional per THIS implementation's declared type (not the audit twin's nullable), and limit unbounded because declared bounds would refuse values the shipped verb accepts. environmentId stays transport-level per #9741 via the TransportScopedMetaRequest spelling at the door; the two anti-freezing widenings (ref.type plain string, no ref.name regex) follow the #12038 1C reasoning. The de-cast door's wire payload is byte-identical and the 501 refusal stays untouched.

The overnight both-sides-re-anchored merge conflict was resolved by re-deriving the system-context.mdx anchors on the merged tree (check-system-context-census: OK — 109 sites, 145 anchors) and regenerating protocol.mdx with the repo tooling, discharging the os-regen deferral.

Releasing per the maintainer's authorization (2026-08-31): marking ready and arming auto-merge; needs:contract-review comes off both carriers.


Generated by Claude Code

@os-project-manager
os-project-manager marked this pull request as ready for review August 31, 2026 00:47
@os-project-manager
os-project-manager added this pull request to the merge queue Aug 31, 2026
Merged via the queue into main with commit ec4c4d2 Aug 31, 2026
38 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-12005-history-meta-item-declared branch August 31, 2026 01:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

2 participants